package pulpfizz.physics;

import java.util.ArrayList;

import org.jbox2d.dynamics.Body;

/**
 * <p>
 * Superclass for userdata that lives on a Body.
 * Note that while often this will be unique per-body,
 * that is not necessary - multiple bodies may be associated
 * with a single Actor, though only one actor may be associated
 * with a body.  Often it will make perfect sense
 * to associate a single Actor with a collection of bodies,
 * such as when using a ragdoll.
 * 
 * @author eric
 */
public class Actor {
	/** 
	 * A useless uninformative actor object.  Used to avoid NPEs when downcasting userdata.
	 * The NewWorld::createBody function automatically sets body userdata to
	 * this value if the body definition does not set its own userdata.
	 */
	static public Actor NULL_ACTOR;
	
	/**
	 * Create and initialize our NULL_ACTOR.
	 */
	static {
		NULL_ACTOR = new Actor();
		NULL_ACTOR.m_id = 0;
		NULL_ACTOR.setName("Unknown body");
		idCount = 1;
	}
	
	/** 32, since group membership is marked as bit flags on an int value. */
	static final public int MAX_GROUPS = 32;
	
	/** Total count of actors, used to generate unique ids */
	static private int idCount;
	
	/** Unique id */
	int m_id;
	
	/** Group membership flags - used in ContactEventDispatcher */
	int m_groupFlags = 0;
	
	/** List of bodies that are sharing this actor. */
	ArrayList<Body> m_bodies;
	
	/** Name of the actor. */
	String m_name;
	
	/**
	 * Default constructor.  Sets a unique id, but doesn't associate
	 * any bodies or put the actor in any groups.  Gives a generic
	 * name, "Actor #(id number)".
	 */
	public Actor() {
		m_id = idCount++;
		m_bodies = new ArrayList<Body>();
		m_name = "Actor #"+m_id;
	}
	
	/**
	 * A convenience constructor to create an actor from an array of Body objects.
	 * Gives the Actor a unique id, and associates all Bodies in the list.  No
	 * groups are associated, and the Actor is given a generic name.
	 * @param bodies
	 */
	public Actor(Body ... bodies)
	{
		this();
		for (Body b : bodies)
		{
			addBody(b);
		}
	}
	
	/**
	 * Get an actor's name.
	 * @return Actor name
	 */
	public String getName() {
		return m_name;
	}
	
	/**
	 * Give this actor a more informative name than
	 * merely its id number.
	 * 
	 * @param newName
	 */
	public void setName(String newName) {
		m_name = newName;
	}
	
	/**
	 * Get the group flags of this actor.
	 * <p>
	 * <em>Warning</em>: to check group membership, you usually
	 * want to call isInGroup(int groupNum) rather than this function.
	 * Especially if you don't know anything about bit flags on integers...
	 * </p>
	 * @return A 32-bit integer whose bits represent group membership.
	 */
	public int getGroupFlags() {
		return m_groupFlags;
	}
	
	/** 
	 * Set the group flags for this actor.
	 * <p>
	 * <em>Warning</em>: to add an actor to a group, it is
	 * usually better to use addToGroup(int) instead of this
	 * function, because the integer passed to that is the group
	 * number.  Passing "4" to this function will
	 * <em>not</em> mark this actor as belonging to group 4!!!
	 * </p>
	 */
	public void setGroupFlags(int groupFlags) {
		if (this == NULL_ACTOR) return;
		m_groupFlags = groupFlags;
	}
	
	/** Is the actor a member of this group? */
	public boolean isInGroup(int groupNum) {
		if (this == NULL_ACTOR) return false;
		int toCheck = 1<<groupNum;
		return (m_groupFlags & toCheck) == toCheck;
	}
	
	/** Add the actor to this group. */
	public void addToGroup(int groupNum) {
		assert(groupNum < MAX_GROUPS);
		if (this == NULL_ACTOR) return;
		int tmp = 1<<groupNum;
		m_groupFlags |= tmp;
	}
	
	/** Remove the actor from this group. */
	public void removeFromGroup(int groupNum) {
		assert(groupNum < MAX_GROUPS);
		if (this == NULL_ACTOR) return;
		int tmp = 1<<groupNum;
		m_groupFlags &= ~tmp;
	}
	
	/** 
	 * Get the unique id for this actor.
	 * Note in particular that NULL_ACTORs all have the same
	 * id, and ids are unique per-actor, <em>not</em> per-body.
	 */
	public int getID() {
		return m_id;
	}
	
	/**
	 * Get the list of bodies identified with this actor.
	 * @return
	 */
	public ArrayList<Body> getBodies() {
		return m_bodies;
	}
	
	/**
	 * Add a body to this actor.  This will both
	 * set the body's userdata to this actor and
	 * add the body to the actor's list of bodies.
	 * @param b
	 */
	public void addBody(Body b) {
		if (this == NULL_ACTOR) return;
		b.setUserData(this);
		m_bodies.add(b);
	}
	
	/**
	 * Remove a body from this actor.  This will
	 * set the body's userdata to NULL_ACTOR and
	 * remove the body from the actor's body list.
	 * <p>
	 * Note that this function does <em>not</em>
	 * cause the body to be removed from the world,
	 * that must be done separately.
	 * </p>  
	 * @param b
	 */
	public void removeBody(Body b) {
		if (this == NULL_ACTOR) return;
		b.setUserData(NULL_ACTOR);
		m_bodies.remove(b);
	}
	
	/**
	 * Returns true if collisions should occur between the two
	 * actors.  You may only prevent collisions between objects
	 * using this filter - normal Box2d contact filtering is
	 * still performed.
	 * <BR><BR>
	 * <em>Warning</em>: This should be symmetrically defined, because
	 * JBox2d does not guarantee whether it will be called as
	 * this.shouldCollide(a) or a.shouldCollide(this) - if any
	 * asymmetry exists, it may cause bugs!
	 * @param a The actor to decide whether to check collisions against
	 * @return True if the collision should be processed
	 */
	public boolean shouldCollide(Actor a) {
		return true;
	}
	
	/**
	 * Reset any static variables.  Necessary because when applets
	 * are reloaded in a browser, static variables are not reset.
	 * Should be called when we re-initialize an applet.
	 */
	static public void resetStatics() {
		//TODO: check if resetting statics is actually necessary
		//when using Pulpcore's custom loader.
		idCount = 0;
		NULL_ACTOR = new Actor();
		NULL_ACTOR.m_id = 0;
		NULL_ACTOR.setName("Unknown body");
		idCount = 1;
	}
	
	@Override
	public String toString()
	{
		return getName();
	}
	
	/** Stub for overriding. */
	public void update(int elapsedTime) {
		
	}
}
